热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

声道|下文_音视频开发9.使用ffmpeg将pcm转码mp3实践(C++)

篇首语:本文由编程笔记#小编为大家整理,主要介绍了音视频开发9.使用ffmpeg将pcm转码mp3实践(C++)相关的知识,希望对你有一定的参考价值。@[TO

篇首语:本文由编程笔记#小编为大家整理,主要介绍了音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++)相关的知识,希望对你有一定的参考价值。


@[TOC](音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++))


一、准备环境
  • CentOS环境
  • 安装 ffmpeg 库,并有必要的库(主要是lame:mp3解码库)
  • ffmpeg库安装可参考之前文档

二、一些预备知识

1. 音频格式说明

如前文所述,pcm是音频裸数据,要转成mp3需要进行重采样、编码步骤。常见的PCM格式有8位和16位两种。


  • 8位每一个PCM数据的值由一个字节即8位来表示(0-255)
  • 16位是指每一个PCM数据的值由两个字节即16位来表示,分为高8位和第8位(-32767~32767)

2. 采样频率

采样频率指每秒钟对音频的采样点数,单位为Hz(赫兹)。
如采样频率为44100hz是指每秒钟采集44100个样本点。


3. 声道数

常见的声道数有:


  • 单声道:mono
  • 双声道:stereo,包含左右两声道
  • 2.1声道:在双声道基础上增加了一个低音声道
  • 5.1声道:分别为正面、左前方、右前方、左环绕、右环绕声道、一个低音声道
  • 7.1声道:在5.1声道的基础上,把左右的环绕声道拆分为左右环绕声道以及左右后置声道,主要应用于BD以及现代的电影院

4. 样本大小

例:


  • 1024个16位单声道PCM样本,它的样本大小为102421=2048字节;
  • 1024个16位双声道PCM样本,它的样本大小为102422=4096字节

5. 一帧样本数


  • PCM 一般为1024;
  • MP3 一般为 1152。

6. 参考命令行

ffmpeg -y -ac 1 -ar 16000 -f s16le -i /data/ffmpeg/test/input.pcm -c:a libmp3lame -q:a 2 /data/ffmpeg/test/output.mp3

三、几个重要函数

1. 重采样参数设置两个函数


3.1.1 swr_alloc_set_opts

示例:

SwrContext* swrContext = NULL;
// 设置参数, 1. 重采样上下文 2.输出声道布局 4.输出采样率, 5.输入声道布局 6.输入样本格式 7.输入采样率 8.配音 9.日志
swrContext = swr_alloc_set_opts(swrContext, avCodecContext->channel_layout, avCodecContext->sample_fmt, avCodecContext->sample_rate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100,
0, 0);

3.1.2 av_opt_set_int

示例:

SwrContext *swrContext = swr_alloc();
// 通道布局:立体声
av_opt_set_int(swrContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
// 采样率:44100
av_opt_set_int(swrContext, "in_sample_rate", OSR, 0);
// 样本格式 s16交错存储
av_opt_set_sample_fmt(swrContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);

在设置完参数后,要调用swr_init进行初始化。


2. 分配样本数据内存空间


3.2.1 av_samples_alloc_array_and_samples

根据音频格式分配相应大小的内存空间,函数内部会调用 av_samples_alloc ,示例代码:

result = av_samples_alloc_array_and_samples(&input_data, &input_linesize, 2, avFrame->nb_samples, AV_SAMPLE_FMT_S16, 0);

3.2.2 av_samples_alloc

根据音频格式分配相应大小的内存空间。用于转换过程中对输出内存大小进行调整。


3. 整体流程


四、实现代码

1. CMakeLists.txt

cmake_minimum_required(VERSION 3.17)
project(ffmpeg_demo)
# 设置ffmpeg依赖库及头文件所在目录,并存进指定变量
set(ffmpeg_libs_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
set(ffmpeg_headers_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
#对于find_package找不到的外部依赖库,可以用add_library添加
# SHARED表示添加的是动态库
# IMPORTED表示是引入已经存在的动态库
add_library( avcodec SHARED IMPORTED)
add_library( avfilter SHARED IMPORTED )
add_library( swresample SHARED IMPORTED )
add_library( swscale SHARED IMPORTED )
add_library( avformat SHARED IMPORTED )
add_library( avutil SHARED IMPORTED )
#指定所添加依赖库的导入路径
set_target_properties( avcodec PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavcodec/libavcodec.so )
set_target_properties( avfilter PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavfilter/libavfilter.so )
set_target_properties( swresample PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libswresample/libswresample.so )
set_target_properties( swscale PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libswscale/libswscale.so )
set_target_properties( avformat PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavformat/libavformat.so )
set_target_properties( avutil PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavutil/libavutil.so )
# 添加头文件路径到编译器的头文件搜索路径下,多个路径以空格分隔
include_directories( $ffmpeg_headers_DIR )
link_directories($ffmpeg_libs_DIR )
link_directories(/usr/lib)
set(CMAKE_CXX_STANDARD 14)
# add_executable(ffmpeg_demo main.cpp)
add_executable(ffmpeg_demo pcm_to_mp3.cpp)
target_link_libraries($PROJECT_NAME avcodec avformat avutil swresample swscale swscale avfilter )

2. 主文件

#include <iostream>
#ifdef __cplusplus
extern "C"

#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
#include "libavutil/log.h"
#include "libswresample/swresample.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#ifdef __cplusplus

#endif
using namespace std;
#define CHANNEL 2
#define OSR 44100
/**
* pcm 转 mp3格式,输入文件路径
*/

int pcm_to_mp3(const char *pcm_file_path, const char *mp3_file_path)

FILE *pcm_file &#61; NULL;
FILE *mp3_file &#61; NULL;
int result;
// 获取mp3编码器
cout << "获取mp3编码器" << endl;
const AVCodec *avCodec &#61; avcodec_find_encoder(AV_CODEC_ID_MP3);
if (!avCodec)

cout << "初始化mp3 编码器失败" << endl;
return -1;

// 创建编码器上下文
AVCodecContext *avCodecContext &#61; avcodec_alloc_context3(avCodec);
if (!avCodecContext)

cout << "avcodec_alloc_context3 失败" << avCodecContext << endl;
return -1;

avCodecContext->bit_rate &#61; 64000;
avCodecContext->channels &#61; CHANNEL;
avCodecContext->channel_layout &#61; AV_CH_LAYOUT_STEREO;
avCodecContext->sample_rate &#61; OSR;
avCodecContext->sample_fmt &#61; AV_SAMPLE_FMT_S16P;
avCodecContext->time_base &#61; av_get_time_base_q();
// 打开编码器
cout << "打开mp3编码器" << endl;
result &#61; avcodec_open2(avCodecContext, avCodec, NULL);
if (result < 0)

cout << "avcodec_open2失败: " << result << endl;
return result;

cout << "打开mp3文件" << mp3_file_path << endl;
// 打开输出文件
mp3_file &#61; fopen(mp3_file_path, "wb");
if (!mp3_file)

cout << "打开mp3文件失败" << endl;
return -1;

// AVFrame 接受重采样的每一帧的音频数据 每帧的样本大小为1152
AVFrame *avFrame &#61; av_frame_alloc();
if (!avFrame)

cout << "分配avFrame帧失败" << endl;
return -1;

// mp3一帧的样本数为1152
avFrame->nb_samples &#61; 1152;
avFrame->channels &#61; CHANNEL;
avFrame->channel_layout &#61; AV_CH_LAYOUT_STEREO;
avFrame->format &#61; AV_SAMPLE_FMT_S16P;
// 给帧分配内存空间
result &#61; av_frame_get_buffer(avFrame, 0);
if (result < 0)

cout << "分配帧内存失败" << endl;
return result;

// 重采样 创建音频重采样上下文
cout << "配置重采样器上下文" << endl;
SwrContext *swrContext &#61; swr_alloc();
if (!swrContext)

cout << "配置重采样上下文失败" << endl;
return -1;

// 设置重采样输入pcm参数:通道布局:立体声 采样率:44100 样本格式 s16交错存储
av_opt_set_int(swrContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swrContext, "in_sample_rate", OSR, 0);
av_opt_set_sample_fmt(swrContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
// 设置重采样输出mp3参数:通道布局:立体声 采样率:44100 样本格式 s16平面存储
av_opt_set_int(swrContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swrContext, "out_sample_rate", OSR, 0);
av_opt_set_sample_fmt(swrContext, "out_sample_fmt", AV_SAMPLE_FMT_S16P, 0);
// 重采样初始化
result &#61; swr_init(swrContext);
if (result < 0)

cout << "重采样器初始化失败,error&#61;" << result << endl;
return result;

uint8_t **input_data &#61; NULL;
uint8_t **output_data &#61; NULL;
int input_linesize, output_linesize;
// 打开pcm文件
cout << "打开源 pcm 文件 " << pcm_file_path << endl;
pcm_file &#61; fopen(pcm_file_path, "rb");
if (!pcm_file)

cout << "打开 pcm 文件失败" << endl;
return -1;

cout << "开始编码转换" << endl;
// 给pcm文件数据分配空间
result &#61; av_samples_alloc_array_and_samples(&input_data, &input_linesize, 2, avFrame->nb_samples, AV_SAMPLE_FMT_S16, 0);
if (result < 0)

cout << "给pcm文件分配空间失败, result &#61; " << result << endl;
return result;

// 缓存重采样数据的空间分配
result &#61; av_samples_alloc_array_and_samples(&output_data, &output_linesize, 2, avFrame->nb_samples, AV_SAMPLE_FMT_S16P, 0);
if (result < 0)

cout << "获取mp3 重采样数据失败, result&#61;" << result << endl;
return result;

// 存放编码后的数据
AVPacket *avPacket &#61; av_packet_alloc();
if (!avPacket)

cout << "分配 avPacket 内存失败" << endl;
return -1;

cout << "&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;循环读入帧&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;" << endl;
long total_size &#61; 0;
while (!feof(pcm_file))

long read_size &#61; (long)fread(input_data[0], 1, avFrame->nb_samples * 4, pcm_file);
total_size &#43;&#61; read_size;
if ((total_size / read_size) % 50 &#61;&#61; 0)

cout << "读取数据:" << read_size << "字节; 累计:" << total_size << " 字节 " << endl;

if (read_size <&#61; 0)

break;

// 重采样
result &#61; swr_convert(swrContext, output_data, avFrame->nb_samples, (const uint8_t **)input_data, avFrame->nb_samples);
if (result < 0)

cout << "音频编码失败,错误信息" << result << endl;
return result;

// 将重采样后的数据存入frame&#xff0c;MP3是s16p 先存放左声道的数据 后存放右声道的数据&#xff0c; data[0]是左声道&#xff0c;1是右声道
avFrame->data[0] &#61; output_data[0];
avFrame->data[1] &#61; output_data[1];
// 编码&#xff0c;写入mp3文件&#xff0c;实际上是对frame这个结构体里面的数据进行编码操作,发送到编码线程:使用编码器 和 存储数据的frame
result &#61; avcodec_send_frame(avCodecContext, avFrame);
if (result < 0)

cout << "mp3编码失败,错误信息:" << result << endl;
return result;

while (result >&#61; 0)

// 接收编码后的数据&#xff0c;使用编码器 和 存储编码数据的pkt, 有可能需要多次才能接收完成
result &#61; avcodec_receive_packet(avCodecContext, avPacket);
// AVERROR_EOF表示没有数据了 这两个错误不影响继续接收数据
if (result &#61;&#61; AVERROR_EOF || result &#61;&#61; AVERROR(EAGAIN))

continue;

else if (result < 0)

break;

fwrite(avPacket->data, 1, avPacket->size, mp3_file);
av_packet_unref(avPacket);


// 告诉解码器没有帧了,如果没有这几行的逻辑&#xff0c;在关闭 avCodecContext 可能会提示 * fames left in the queu on closing
avcodec_send_frame(avCodecContext, __null);
while(avcodec_receive_packet(avCodecContext, avPacket)!&#61;AVERROR_EOF);
// 关闭缓存
if (input_data)

av_free(input_data);

if (output_data)

av_free(output_data);

cout << "关闭文件" << endl;
fclose(pcm_file);
fclose(mp3_file);
cout << "释放资源" << endl;
// s释放 frame pkt
av_frame_free(&avFrame);
av_packet_free(&avPacket);
// 释放重采样上下文
swr_free(&swrContext);
// 释放编码器上下文
avcodec_free_context(&avCodecContext);
cout << "转码完成" << endl;
return 0;

int main(int argc, char *argv[])

const char *input &#61; "input.pcm";
const char *output &#61; "out.mp3";
pcm_to_mp3(input, output);
return 1;

程序执行结果&#xff1a;

./ffmpeg_demo
获取mp3编码器
打开mp3编码器
打开mp3文件out.mp3
配置重采样器上下文
打开源 pcm 文件 input.pcm
开始编码转换
&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;循环读入帧&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;
读取数据:4608字节; 累计:230400 字节
读取数据:4608字节; 累计:460800 字节
读取数据:4608字节; 累计:691200 字节
读取数据:4608字节; 累计:921600 字节
关闭文件
释放资源
转码完成

推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • Java图形化计算器设计与实现
    本文介绍了使用Java编程语言设计和实现图形化计算器的方法。通过使用swing包和awt包中的组件,作者创建了一个具有按钮监听器和自定义界面尺寸和布局的计算器。文章还分享了在图形化界面设计中的一些心得体会。 ... [详细]
  • 花瓣|目标值_Compose 动画边学边做夏日彩虹
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Compose动画边学边做-夏日彩虹相关的知识,希望对你有一定的参考价值。引言Comp ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 今日份分享:Flutter自定义之旋转木马
    今日份分享:Flutter自定义之旋转木马-先上图,带你回到童年时光:效果分析子布局按照圆形顺序放置且平分角度子布局旋转、支持手势滑动旋转、快速滑动抬手继续旋转、自动旋转支持X轴旋 ... [详细]
  • 1简介本文结合数字信号处理课程和Matlab程序设计课程的相关知识,给出了基于Matlab的音乐播放器的总体设计方案,介绍了播放器主要模块的功能,设计与实现方法.我们将该设 ... [详细]
  • android 触屏处理流程,android触摸事件处理流程 ? FOOKWOOD「建议收藏」
    android触屏处理流程,android触摸事件处理流程?FOOKWOOD「建议收藏」最近在工作中,经常需要处理触摸事件,但是有时候会出现一些奇怪的bug,比如有时候会检测不到A ... [详细]
author-avatar
佩弦_秦子轩_188
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有